1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
|
import { Separator } from "@/components/ui/separator";
import { getRfqWithDetails, getRfqVendorResponses } from "@/lib/rfq-last/service";
import { Alert, AlertTitle, AlertDescription } from "@/components/ui/alert";
import { AlertCircle, Users, Send, FileText, CheckCircle2, Clock, XCircle } from "lucide-react";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Badge } from "@/components/ui/badge";
import { RfqVendorTable } from "@/lib/rfq-last/vendor/rfq-vendor-table";
import { VendorResponseStatusCard } from "@/lib/rfq-last/vendor/vendor-response-status-card";
interface VendorPageProps {
params: {
lng: string;
id: string;
};
searchParams: Promise<Record<string, any>>;
}
export default async function VendorPage(props: VendorPageProps) {
const resolvedParams = await props.params;
const rfqId = parseInt(resolvedParams.id, 10);
if (!rfqId || isNaN(rfqId) || rfqId <= 0) {
return (
<div className="p-4">
<Alert variant="destructive">
<AlertCircle className="h-4 w-4" />
<AlertTitle>오류</AlertTitle>
<AlertDescription>유효하지 않은 RFQ입니다.</AlertDescription>
</Alert>
</div>
);
}
// RFQ 기본 정보 및 벤더 목록 가져오기
const { data: rfqData, success: rfqSuccess } = await getRfqWithDetails(rfqId);
const { data: vendorResponses, success: responsesSuccess } = await getRfqVendorResponses(rfqId);
if (!rfqSuccess || !rfqData ||!responsesSuccess||!vendorResponses) {
return (
<div className="p-4">
<Alert variant="destructive">
<AlertCircle className="h-4 w-4" />
<AlertTitle>오류</AlertTitle>
<AlertDescription>데이터를 불러오는데 실패했습니다.</AlertDescription>
</Alert>
</div>
);
}
// 응답 상태별 집계
const statusSummary = {
total: rfqData?.details.length || 0,
invited: vendorResponses?.filter(v => v.status === "초대됨").length || 0,
drafting: vendorResponses?.filter(v => v.status === "작성중").length || 0,
submitted: vendorResponses?.filter(v => v.status === "제출완료").length || 0,
confirmed: vendorResponses?.filter(v => v.status === "최종확정").length || 0,
cancelled: vendorResponses?.filter(v => v.status === "취소").length || 0,
};
return (
<div className="space-y-6">
<div>
<h3 className="text-lg font-medium">벤더 관리</h3>
<p className="text-sm text-muted-foreground">
견적 요청을 보낼 벤더를 관리하고 응답 상태를 확인합니다.
</p>
</div>
<Separator />
{/* 응답 상태 요약 카드 */}
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-5">
<VendorResponseStatusCard
title="전체 벤더"
count={statusSummary.total}
icon={Users}
variant="default"
/>
<VendorResponseStatusCard
title="초대됨"
count={statusSummary.invited}
icon={Send}
variant="secondary"
/>
<VendorResponseStatusCard
title="작성중"
count={statusSummary.drafting}
icon={Clock}
variant="warning"
/>
<VendorResponseStatusCard
title="제출완료"
count={statusSummary.submitted}
icon={CheckCircle2}
variant="success"
/>
<VendorResponseStatusCard
title="최종확정"
count={statusSummary.confirmed}
icon={FileText}
variant="primary"
/>
</div>
{/* 벤더 목록 테이블 */}
<Card>
<CardHeader>
<div className="flex items-center justify-between">
<div className="space-y-1">
<CardTitle className="text-base font-semibold">
벤더 목록
</CardTitle>
<CardDescription>
Short List 확정, 견적 비교, RFQ 발송 및 응답 관리, AVL 연동 등<br />
종합적인 벤더 관리 기능을 제공합니다.
</CardDescription>
</div>
<Badge variant="outline" className="font-mono">
{statusSummary.total} Vendors
</Badge>
</div>
</CardHeader>
<CardContent>
<RfqVendorTable
rfqId={rfqId}
rfqCode={rfqData.rfqCode }
rfqDetails={rfqData.details || []}
vendorResponses={vendorResponses || []}
/>
</CardContent>
</Card>
</div>
);
}
|